#include <sstream>
#include "SkyeyeImpl.h"

#include "skyeye/skyeye_mach.h"
#include "skyeye/skyeye_arch.h"
#include "skyeye/skyeye_pref.h"
#include "skyeye/skyeye_config.h"
#include "skyeye/skyeye_callback.h"
#include "skyeye/skyeye_bus.h"
#include "skyeye/skyeye_ram.h"
#include "skyeye/sim_control.h"

using namespace etb;

SkyeyeImpl* SkyeyeImpl::impl_ = NULL;

extern "C" void ARM9DASM(unsigned int, char *);
extern "C" int io_read(short size, uint32_t addr, uint32_t * value);
extern "C" int bus_write(short size, int addr, uint32_t value);

SkyeyeImpl::~SkyeyeImpl()
{
  sky_pref_t *sp = get_skyeye_pref();
  free(sp->module_search_dir);
  free(sp->conf_filename);
}

void SkyeyeImpl::initMach()
{
  sky_pref_t *sp = get_skyeye_pref();
  if (sp == NULL) return;
  set_module_search_dir(strdup(MODULE_PATH));
  set_interactive_mode(False);
  set_conf_filename(strdup(CONF_FILENAME));

  FILE *conf = fopen(CONF_FILENAME, "w");
  fwrite(CONF_CONTENT_NOR, sizeof(char), strlen(CONF_CONTENT_NOR), conf);
  fflush(conf);

  SIM_init();

  register_callback(SkyeyeImpl::busWriteCallback, Bus_write_callback);
  SIM_start();
}

void SkyeyeImpl::runMach()
{
  SIM_run();
}

void SkyeyeImpl::continueMach()
{
  SIM_run();
}

void SkyeyeImpl::stopMach()
{
  SIM_stop(get_arch_instance(""));
  SIM_start();
}

void SkyeyeImpl::pauseMach()
{
  SIM_stop(get_arch_instance(""));
}

void SkyeyeImpl::stepRun()
{
  skyeye_stepi(1);
}

U32 SkyeyeImpl::writeToMemory(U8 *ptr, U32 addr, U32 size)
{
  U32 nSize = size / 4 + 1;

  for (U32 i = 0; i < nSize; ++i) {
    bus_write(sizeof(U32) * 8, addr + i, *((U32*)ptr + i));
  }

  // TODO: lack of endianness checking and written size may be wrong
  return size;
}

void SkyeyeImpl::setPc(U32 addr)
{
  SIM_stop(get_arch_instance(""));
  get_arch_instance("")->set_pc(addr);
  SIM_run();
}

U32 SkyeyeImpl::getInstruction()
{
  U32 step;

  generic_arch_t* arch = get_arch_instance("");
  step = arch->get_instr();

  //ARM9DASM(step, str);
  //std::cout << "0x" << std::hex << addr << ": " << 
  //    str << ", 0x" << step << std::endl;;

  return step;
}

void SkyeyeImpl::dumpRegs(std::vector<U32>& regs)
{
  generic_arch_t* arch = get_arch_instance("");

  for (int i = 0; i < 17; ++i)
    regs.push_back(arch->get_regval_by_id(i));
}

void SkyeyeImpl::keyPressed(int keyNum)
{
  if (keyNum > 6) return;

  machine_config_t* mach = get_current_mach();

  U32 data = mach->mach_io_read_word(NULL, GPGDAT);
  setBit32(&data, 1, keyNum - 1);
  mach->mach_io_write_word(NULL, GPGDAT, data);
}

void SkyeyeImpl::keyReleased(int keyNum)
{
  if (keyNum > 6) return;

  machine_config_t* mach = get_current_mach();

  U32 data = mach->mach_io_read_word(NULL, GPGDAT);
  setBit32(&data, 0, keyNum - 1);
  mach->mach_io_write_word(NULL, GPGDAT, data);
}

void SkyeyeImpl::busWriteCallback(generic_arch_t* arch_instance)
{
  if (arch_instance == NULL) return;
  bus_recorder_t* br = get_last_bus_access();

  if (br && br->addr == (U32)LCDSADDR1) {

    void * p = get_dma_addr(br->value << 1);
    std::ostringstream oss(std::ostringstream::out);
    oss << ETB_CMD_PARAM_ADDR << ":" << std::hex << 
        reinterpret_cast<uintptr_t>(p);
    SkyeyeImpl::getInst()->getCallback()->handleCallback(
        ETB_CMD_DEV_LCD, oss.str());

  } else if (br && br->addr == (U32)GPBDAT) {

    if ( !(br->value & (1 << 5)) ) {
      SkyeyeImpl::getInst()->getCallback()->handleCallback(
          ETB_CMD_DEV_LED1, ETB_CMD_PARAM_ON);
    } else {
      SkyeyeImpl::getInst()->getCallback()->handleCallback(
          ETB_CMD_DEV_LED1, ETB_CMD_PARAM_OFF);
    }
    if ( !(br->value & (1 << 6)) ) {
      SkyeyeImpl::getInst()->getCallback()->handleCallback(
          ETB_CMD_DEV_LED2, ETB_CMD_PARAM_ON);
    } else {
      SkyeyeImpl::getInst()->getCallback()->handleCallback(
          ETB_CMD_DEV_LED2, ETB_CMD_PARAM_OFF);
    }
    if ( !(br->value & (1 << 7)) ) {
      SkyeyeImpl::getInst()->getCallback()->handleCallback(
          ETB_CMD_DEV_LED3, ETB_CMD_PARAM_ON);
    } else {
      SkyeyeImpl::getInst()->getCallback()->handleCallback(
          ETB_CMD_DEV_LED3, ETB_CMD_PARAM_OFF);
    }
    if ( !(br->value & (1 << 8)) ) {
      SkyeyeImpl::getInst()->getCallback()->handleCallback(
          ETB_CMD_DEV_LED4, ETB_CMD_PARAM_ON);
    } else {
      SkyeyeImpl::getInst()->getCallback()->handleCallback(
          ETB_CMD_DEV_LED4, ETB_CMD_PARAM_OFF);
    }
  }

}

void SkyeyeImpl::setBit32(U32 *data, U8 value, U8 offset)
{
  U32 v = *data;

  if (value == 1)
    v |= (1 << offset);
  else if (value == 0)
    v &= ~(1 << offset);
  else
    return;

  *data = v;
}

U32 SkyeyeImpl::getLcdBaseAddr()
{
  U32 data = 0;

  io_read(32, LCDSADDR1, &data);
  if (data == 0) return 0;

  void * p = get_dma_addr(data << 1);

  return reinterpret_cast<uintptr_t>(p);
}
